require 'barber'
require 'coffee-script'
require 'fileutils'
require 'pyro/linker'
require 'sass'
require 'sinatra/base'

# Namespaces Pyro::Compiler.
module Pyro
  # Public: Compilation helpers for Pyro::App.
  #
  # * Used by Pyro::Helpers.
  module Compiler
    include Linker

    # Public: Compiles source .js, .coffee, .css, .scss,
    #         .hbs, and .handlebars files, and writes the
    #         output to target files.
    #
    # * Sets Pyro::App.settings.args to the args passed in from the calling
    #   helper so the calling args don't have to be passed around all the time.
    # * Pyro::App.settings.args is reset to nil after it's done.
    #
    # args - The arguments from script(), stylesheet(), or template().
    # ext  - The file extensions to look for when using the dir argument.
    #
    # Example
    #
    #   compile(args.first, 'js,coffee')
    #
    # Returns html tags linking to the compiled files.
    def compile(args, ext)
      tags = ''
      Pyro::App.set :args, args
      replace_srcs_with_prod_srcs() if settings.environment == :production
      find_sources(ext).each do |file|
        target = find_target(file, (ext == 'css,scss' ? '.css' : '.js'))
        FileUtils.mkdir_p File.dirname(target)
        IO.write target, render_contents(file), mode: 'a'
        tags << generate_link(target)
      end
      Pyro::App.set :args, nil
      tags
    end

    # Public: Find source files to compile.
    #
    # ext - The file extensions to look for when using the dir argument.
    #
    # Example
    #
    #   find_sources(ext)
    #
    # Returns an Array of paths.
    def find_sources(ext)
      find_sources_from_src + find_sources_from_dir(ext)
    end

    # Public: Finds a single source file from the src argument.
    #
    # Example
    #
    #   find_sources_from_src
    #
    # Returns an Array with a path, or an empty Array if src wasn't set.
    def find_sources_from_src
      settings.args[:src] ? ["#{settings.root}/#{settings.args[:src]}"] : []
    end

    # Public: Finds all the source files in the directory passed in from the
    #         dir argument that match the ext argument.
    #
    # ext - The file extensions to look for.
    #
    # Example
    #
    #   find_sources_from_dir(ext)
    #
    # Returns an Array of paths.
    def find_sources_from_dir(ext)
      sources = []
      if settings.args[:dir]
        dir = "#{settings.root}/#{settings.args[:dir]}/**/*.{#{ext}}"
        Dir.glob(dir).each { |path| sources << path }
      end
      sources
    end

    # Public: Finds the target path/file to write
    #         rendered sources filecontents into.
    #
    # file - The file to look for.
    # ext  - The file extension to use.
    #
    # Example
    #
    #   find_target(file, (ext == 'css,scss' ? '.css' : '.js'))
    #
    # Returns a path.
    def find_target(file, ext)
      if settings.args[:target]
        target = settings.args[:target]
      else
        target = file.sub(".#{file.split('.').last}", ext)
      end
      target.sub("#{settings.root}/", '')
            .insert(0, "#{settings.public_folder}/")
    end

    # Public: Renders the contents of a source file.
    #
    # file - The source file to render from.
    #
    # Example
    #
    #   render_contents(file)
    #
    # Returns the rendered contents.
    def render_contents(file)
      case File.extname(file)
      when '.coffee'
        CoffeeScript.compile File.read(file), bare: true
      when '.scss'
        Sass::Engine.new(File.read(file), { syntax: :scss }).render
      when '.hbs', '.handlebars'
        template = Barber::Ember::FilePrecompiler.call(File.read(file))
        "Ember.TEMPLATES['#{template_name(file)}'] = #{template}"
      else
        File.read(file)
      end
    end

    # Public: Replaces src and dir arguments with
    #         prod_src and prod_dir arguments (if available).
    #
    # * Used for compiling in the production environment.
    #
    # Example
    #
    #   replace_srcs_with_prod_srcs()
    #
    # Overwrites the src and dir arguments, but returns nothing.
    def replace_srcs_with_prod_srcs
      if settings.args[:prod_src]
        settings.args[:src] = settings.args[:prod_src]
      end
      if settings.args[:prod_dir]
        settings.args[:dir] = settings.args[:prod_dir]
      end
    end
  end
end